#pragma once

#include <ostream>
#include <string>
#include <type_traits>

namespace math {

template<typename T>
T gcd(const T& a, const T& b) {
    return b == 0 ? a : gcd(b, a % b);
}

#if __cplusplus >= 202002L

#include <concepts>

template<typename T>
concept floating_point = std::is_floating_point<T>::value;

template<typename T>
concept arithmetic = std::is_arithmetic<T>::value;

#endif

class Fraction {
    long long m_numerator;
    long long m_denominator;
    bool m_minus;

    inline void m_reduct();

public:
    Fraction(long long numerator, long long denominator);
    explicit Fraction(std::string x);
    Fraction(Fraction& frac);
    
#if __cplusplus >= 202002L

template<floating_point T>
Fraction(T x) : Fraction(std::to_string(x)) {}

template<std::integral T>
Fraction(T x) : Fraction(x, 1) {}

#else

template<typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true>
Fraction(T x) : Fraction(std::to_string(x)) {}

template<typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
Fraction(T x) : Fraction(x, 1) {}

#endif

    ~Fraction() = default;
    
    Fraction& operator-() const;
    Fraction& operator~() const;
    Fraction& operator+(const Fraction& frac) const;
    Fraction& operator-(const Fraction& frac) const;
    Fraction& operator*(const Fraction& frac) const;
    Fraction& operator/(const Fraction& frac) const;

    [[nodiscard]] inline long long getNumerator() const;
    [[nodiscard]] inline long long getDenominator() const;
    [[nodiscard]] inline bool getMinus() const;
    
    [[nodiscard]] float toFloat() const;
    [[nodiscard]] double toDouble() const;
    [[nodiscard]] long double toLDouble() const;
    
    friend std::ostream& operator<<(std::ostream& os, const Fraction& frac);
};

#if __cplusplus >= 202002L

#include <concepts>

template<arithmetic T>
Fraction& operator+(T x, Fraction& frac) {
    return frac + x;
}

template<arithmetic T>
Fraction& operator-(T x, Fraction& frac) {
    return -(frac - x);
}

template<arithmetic T>
Fraction& operator*(T x, Fraction& frac) {
    return frac * x;
}

template<arithmetic T>
Fraction& operator/(T x, Fraction& frac) {
    return ~(frac / x);
}

#else

template<typename T, std::enable_if_t<std::is_arithmetic<T>::value, bool> = true>
Fraction& operator+(T x, Fraction& frac) {
    return frac + x;
}

template<typename T, std::enable_if_t<std::is_arithmetic<T>::value, bool> = true>
Fraction& operator-(T x, Fraction& frac) {
    return -(frac - x);
}

template<typename T, std::enable_if_t<std::is_arithmetic<T>::value, bool> = true>
Fraction& operator*(T x, Fraction& frac) {
    return frac * x;
}

template<typename T, std::enable_if_t<std::is_arithmetic<T>::value, bool> = true>
Fraction& operator/(T x, Fraction& frac) {
    return ~(frac / x);
}
#endif

};

